#ifndef NOTE_H
#define NOTE_H

#include <QDebug>

#include <QString>
#include <QPixmap>

#include <qmath.h>

#include "conversions.h"

class PitchNote;
class AlterNote;
class EquiNote;
class Clef;
class Octave;
class Note;

/* CLASS containing pitch note, provide conversion */
class PitchNote
{
public:
    // Constructors
    PitchNote(quint8 height = 0)
        : height_ (height)
    {
        Q_ASSERT_X(height_ <= 6, "class PitchNote", "heitgh must be <= 6.");
    }
    PitchNote(QString str);
    PitchNote(EquiNote eq);

    // Operators
    bool operator==(PitchNote pt) { return toInt() == pt.toInt(); }

    // Conversion
    quint8 toInt() { return height_; }
    QString toQString(bool disp = true, bool lat = false) { return PitchNoteList(disp, lat).at(height_); }
    EquiNote toEquiNote();

    // Computation
    int getCentsOffset(Temperament = Equal);
private:
    // A = 0, B = 1, ..., G = 6
    quint8 height_;

};

/* CLASS containing alterations, provide conversion */
class AlterNote
{
public:
    // Constructors
    AlterNote(qint8 offset = 0, bool unsign = false)
        : offset_ (unsign ? offset - 2 : offset)
    {
        if (offset_ < -2 && offset_ > 2) qWarning() << "class AlterNote, offset must be <= 2 and >= 2. Is" << offset;
        //Q_ASSERT_X(offset_ >= 2 && offset_ <= 2, "class AlterNote", "offset must be <= 2 and >= 2.");
    }

    AlterNote(QString str);
    AlterNote(EquiNote eq);

    // Operators
    bool operator==(AlterNote alt) { return toInt() == alt.toInt(); }

    // Conversion
    quint8 toUnsignedInt() { return offset_ + 2; }
    qint8 toInt() { return offset_; }
    QString toQString(bool disp = true) { return AlterNoteList(disp).at(toUnsignedInt()); }

    // Computation
    int getCentsOffset(Temperament = Equal) { return int(offset_) * 100; }

    // Pixmap
    QString getPathToImage() { QString s = ":/icons/" + toQString(false) + ".png"; return s; }
    QPixmap getQPixmap() { return QPixmap(getPathToImage()); }
private:
    // bb = -2, b = -1, ..., # = 1, ## = 2
    qint8 offset_;

};

/* CLASS containing equivalent notes, provide conversion */
class EquiNote
{
public:
    // Constructors
    EquiNote(quint8 height = 0)
        : height_ (height)
    {
        Q_ASSERT_X(height_ <= 11, "class EquiNote", "offset must be <= 11.");
    }

    EquiNote(QString str);
    EquiNote(PitchNote pt, AlterNote alt) { height_ = (pt.toEquiNote().toInt() + alt.toInt() + 12) % 12; }

    // Operators
    bool operator==(EquiNote eq);

    // Conversion
    quint8 toInt() { return height_; }
    QString toQString(bool disp = true, bool lat = false) { return EquiNoteList(disp, lat).at(height_); }
    PitchNote toPitchNote();
    AlterNote toAlterNote();

    // Computation
    int getCentsOffset(Temperament = Equal) { return ((int(height_) + 9) % 12 - 9) * 100; }
private:
    // A_ = 0, Bb = 1, B_ = 2, ..., Ab = 11
    quint8 height_;

};

/* CLASS containing clef, provide conversion */
class Clef
{
public:
    // Constructors
    Clef(quint8 clef = 0)
        : clef_ (clef)
    {

        Q_ASSERT_X(clef_ <= 2, "class Clef", "offset must be <= 2.");
    }
    Clef(QString str);

    // Operators
    bool operator==(Clef clef) { return toInt() == clef.toInt(); }

    // Conversion
    quint8 toInt() { return clef_; }
    QString toQString(bool disp = true) { return ClefList(disp).at(clef_); }

    // Pixmap
    QString getPathToImage() { QString s = ":/images/clefs/" + toQString(false) + ".png"; return s; }
    QPixmap getQPixmap() { return QPixmap(getPathToImage()); }
private:
    // treble = 0, tenor = 1, bass = 2
    quint8 clef_;

};

/* CLASS containing octave */
struct Octave
{
public:
    // Constructors
    Octave(qint8 oct = 4, bool unsign = false)
        : oct_ (unsign ? oct - 1 : oct)
    {
        if(oct_ > 8) qWarning() << "Octave to high? Is" << oct;
        Q_ASSERT_X(oct_ >= -1, "class Octave", "offset must be >= -1 and <= 6.");
    }
    Octave(QString str);

    // Operators
    bool operator==(Octave oct) { return toInt() == oct.toInt(); }

    // Conversion
    quint8 toUnsignedInt() { return oct_ + 1; }
    qint8 toInt() { return oct_; }
    QString toQString(bool disp = true) { return OctaveList(disp).at(toUnsignedInt()); }

    // Computation
    int getCentsOffset() { return (int(oct_) - 4) * 1200; }
private:
    // cm_1 = -1, c0 = 0, ..., c6 = 6
    qint8 oct_;
};

/* CLASS containing a full note, provide conversion */
class Note
{
public:
    // Constructors
    Note() : pitch_ (PitchNote()), alter_ (AlterNote()), oct_ (Octave()) {}

    Note(PitchNote npitch, AlterNote nalter = AlterNote(), Octave noct = Octave() )
        : pitch_ (npitch), alter_ (nalter), oct_ (noct) {}

    Note(EquiNote nequi, Octave noct = Octave() )
        : pitch_ (nequi.toPitchNote()), alter_ (nequi.toAlterNote()), oct_ (noct) {}

    Note(quint8 npitch, qint8 nalter = -1, qint8 noct = -1, bool unsign_alt = false, bool unsign_oct = false)
        : pitch_ (npitch), alter_ (AlterNote(nalter, unsign_alt)), oct_ (Octave(noct, unsign_oct)) {}

    Note(QString spitch, QString salter = "natural", QString soct = "c4")
        : pitch_ (spitch), alter_ (salter), oct_ (soct) {}

    Note(int offset, Note note, Temperament temp = Equal);

    Note(qreal freq, qreal base = CONCERT_PITCH_FREQUENCY, Note note = Note(), Temperament temp = Equal);

    // Operators
    bool operator==(Note note);

    // Conversion
    PitchNote toPitchNote() { return pitch_; }
    AlterNote toAlterNote() { return alter_; }
    EquiNote toEquiNote();
    Octave toOctave() { return oct_; }

    QString toQString() { return getPitchNoteName() + getAlterNoteName() + getOctaveName(); }

    // Computation
    QString getPitchNoteName(bool disp = true, bool lat = false) { return pitch_.toQString(disp, lat); }
    QString getAlterNoteName(bool disp = true) { return alter_.toQString(disp); }
    QString getEquiNoteName(bool disp = true, bool lat = false) { return toEquiNote().toQString(disp, lat); }
    QString getOctaveName(bool disp = true) { return oct_.toQString(disp); }

    QString getPathToImage(Clef clf = Clef(), QString basename = "");
    QPixmap getQPixmap(Clef clf = Clef());

    // Computations
    int getCentsOffset(Temperament temp = Equal);
    int getCentDiff(Note fromNote = Note(), Temperament temp = Equal);
    qreal getFreq(qreal base = CONCERT_PITCH_FREQUENCY, Note baseNote = Note(CONCERT_PITCH_REFERENCE), Temperament temp = Equal);

    // Transpose
    Note transpose(Note frombase, Note tobase = Note("C"));
private:
    PitchNote pitch_;
    AlterNote alter_;
    Octave oct_;

};

#endif // NOTE_H
